/* * Sun Public License Notice * * The contents of this file are subject to the Sun Public License * Version 1.0 (the "License"). You may not use this file except in * compliance with the License. A copy of the License is available at * http://www.sun.com/ * * The Original Code is Forte for Java, Community Edition. The Initial * Developer of the Original Code is Sun Microsystems, Inc. Portions * Copyright 1997-2000 Sun Microsystems, Inc. All Rights Reserved. */ package org.openide.util; import java.lang.ref.WeakReference; import java.beans.*; import java.io.*; import java.util.Map; import java.util.HashMap; /** Shared object that allows different instances of the same class * to share common data. * <p>The data are shared only between instances of the same class (not subclasses). * Thus, such "variables" have neither instance nor static behavior. * * @author Ian Formanek, Jaroslav Tulach */ public abstract class SharedClassObject extends Object implements Externalizable { /** serialVersionUID */ static final long serialVersionUID = 4527891234589143259L; /** property change support (PropertyChangeSupport) */ private static final Object PROP_SUPPORT = new Object (); /** Map (Class, DataEntry) that maps Classes to maps of any objects * @associates DataEntry*/ private static HashMap values = new HashMap (4); /** data entry for this class */ private DataEntry dataEntry; /** hard reference to primary instance of this class * This is here not to allow the finalization till at least * one object exists */ private SharedClassObject first; /** Create a shared object. * Typically shared-class constructors should not take parameters, since there * will conventionally be no instance variables. */ protected SharedClassObject () { dataEntry = getDataEntry (this); } /* Calls a referenceLost to decrease the counter on the shared data. * This method is final so no descendant can override it, but * it calls the method unreferenced() that can be overriden to perform any * additional tasks on finalizing. */ protected final void finalize() throws Throwable { referenceLost (); } /** Indicate whether the shared data of the last existing instance of this class * should be cleared when that instance is finalized. * * Subclasses may perform additional tasks * on finalization if desired. This method should be overridden * in lieu of {@link #finalize}. * <p>The default implementation returns <code>true</code>. * Classes which have precious shared data may want to return <code>false</code>, so that * all instances may be finalized, after which new instances will pick up the same shared variables * without requiring a recalculation. * * @return <code>true</code> if all shared data should be cleared, * <code>false</code> if it should stay in memory */ protected boolean clearSharedData () { return true; } /** Test whether the classes of the compared objects are the same. * @param obj the object to compare to * @return <code>true</code> if the classes are equal */ public final boolean equals (Object obj) { return ((obj instanceof SharedClassObject) && (getClass().equals(obj.getClass()))); } /** Get a hashcode of the shared class. * @return the hash code */ public final int hashCode () { return getClass().hashCode(); } /** Obtain lock for synchronization on manipulation with this * class. * Can be used by subclasses when performing nonatomic writes, e.g. * @return an arbitrary synchronizable lock object */ protected final Object getLock () { return getClass ().toString ().intern (); } /** Obtains data entry for this class. * @param obj the requestor * @return the data entry object */ private DataEntry getDataEntry (SharedClassObject obj) { synchronized (getLock ()) { DataEntry de = (DataEntry)values.get (getClass ()); if (de == null) { de = new DataEntry (); values.put (getClass (), de); } de.increase(); // finds reference for the first object of the class obj.first = de.first (obj); return de; } } /** Should be called from within a finalize method to manage references * to the shared data (when the last reference is lost, the object is * removed) */ private void referenceLost() { /*System.out.println ("Lock: " + getLock()); System.out.println (" DataEntry: " + dataEntry); System.out.println ("Values: " + (values==null));*/ // NOI18N synchronized (getLock ()) { if (dataEntry == null || dataEntry.decrease() == 0) { if (clearSharedData ()) { // clears the data values.remove (getClass()); } } } } /** Set a shared variable. * Automatically {@link #getLock locks}. * @param key name of the property * @param value value for that property (may be null) * @return the previous value assigned to the property, or <code>null</code> if none */ protected final Object putProperty (Object key, Object value) { synchronized (getLock ()) { return dataEntry.getMap (this).put (key, value); } } /** Set a shared variable available only for string names. * Automatically {@link #getLock locks}. * @param key name of the property * @param value value for that property (may be null) * @param notify should all listeners be notified about property change? * @return the previous value assigned to the property, or <code>null</code> if none */ protected final Object putProperty (String key, Object value, boolean notify) { Object previous = putProperty (key, value); if (notify) { firePropertyChange (key, previous, value); } return previous; } /** Get a shared variable. * Automatically {@link #getLock locks}. * @param key name of the property * @return value of the property, or <code>null</code> if none */ protected final Object getProperty (Object key) { synchronized (getLock ()) { return dataEntry.getMap (this).get (key); } } /** Initialize shared state. * Should use {@link #putProperty} to set up variables. * Subclasses should always call the super method. * <p>This method need <em>not</em> be called explicitly; it will be called once * the first time a given shared class is used (not for each instance!). */ protected void initialize () { } /* Adds the specified property change listener to receive property * change events from this action. * @param l the property change listener. * @see java.beans.PropertyChangeListener * @see #removePropertyChangeListener */ public final void addPropertyChangeListener(PropertyChangeListener l) { synchronized (getLock ()) { // System.out.println ("added listener: " + l + " to: " + getClass ()); // NOI18N PropertyChangeSupport supp = (PropertyChangeSupport)getProperty (PROP_SUPPORT); if (supp == null) { // System.out.println ("Creating support"); // NOI18N putProperty (PROP_SUPPORT, supp = new PropertyChangeSupport (this)); } boolean noListener = !supp.hasListeners (null); supp.addPropertyChangeListener(l); if (noListener) { // added first listener // Thread.dumpStack (); // System.out.println ("added first listener to: " + getClass ()); // NOI18N addNotify (); } } } /* * Removes the specified property change listener so that it * no longer receives property change events from this action. * @param l the property change listener. * @see java.beans.PropertyChangeListener * @see #addPropertyChangeListener */ public final void removePropertyChangeListener(PropertyChangeListener l) { synchronized (getLock ()) { PropertyChangeSupport supp = (PropertyChangeSupport)getProperty (PROP_SUPPORT); if (supp == null) return; boolean hasListener = supp.hasListeners (null); supp.removePropertyChangeListener(l); if (hasListener && !supp.hasListeners (null)) { // System.out.println ("removed all listeners to: " + getClass ()); // NOI18N removeNotify (); } } } /** Notify subclasses that the first listener has been added to this action. * The default implementation does nothing. */ protected void addNotify () { } /** Notify subclasses that the last listener has been removed from this action. * The default implementation does nothing. */ protected void removeNotify () { } /** Fire a property change event to all listeners. * @param name the name of the property * @param oldValue the old value * @param newValue the new value */ protected void firePropertyChange ( String name, Object oldValue, Object newValue ) { PropertyChangeSupport supp = (PropertyChangeSupport)getProperty (PROP_SUPPORT); if (supp != null) supp.firePropertyChange (name, oldValue, newValue); } /** Writes nothing to the stream. * @param oo ignored */ public void writeExternal (ObjectOutput oo) throws IOException { } /** Reads nothing from the stream. * @param oi ignored */ public void readExternal (ObjectInput oi) throws IOException, ClassNotFoundException { } /** This method provides correct handling of serialization and deserialization. * When serialized the method writeExternal is used to store the state. * When deserialized first an instance is located by a call to findObject (clazz, true) * and then a method readExternal is called to read its state from stream. * <P> * This allows to have only one instance of the class in the system and work * only with it. * * @return write replace object that handles the described serialization/deserialization process */ protected Object writeReplace () { return new WriteReplace (this); } /** Obtain an instance of the desired class, if there is one. * @param clazz the shared class to look for * @return the instance, or <code>null</code> if such does not exists */ public static SharedClassObject findObject (Class clazz) { return findObject (clazz, false); } /** Find an existing object, possibly creating a new one as needed. * To create a new instance the class must be public and have a public * default constructor. * * @param clazz the class of the object to find (must extend <code>SharedClassObject</code>) * @param create <code>true</code> if the object should be created if it does not yet exist * @return an instance, or <code>null</code> if there was none and <code>create</code> was <code>false</code> * @exception IllegalArgumentException if a new instance could not be created for some reason */ public static SharedClassObject findObject (Class clazz, boolean create) { DataEntry de = (DataEntry)values.get (clazz); // either null or the object SharedClassObject obj = de == null ? null : de.get (); if (obj == null && create) { // try to create new instance try { obj = (SharedClassObject) clazz.newInstance (); } catch (Exception ex) { if (Boolean.getBoolean ("netbeans.debug.exceptions")) // NOI18N ex.printStackTrace (); // cannot create the instance or it is not SharedClassObject throw new IllegalArgumentException (ex.getMessage ()); } } return obj; } /** Class that is used as default write replace. */ static final class WriteReplace extends Object implements Serializable { /** serialVersionUID */ static final long serialVersionUID = 1327893248974327640L; /** the class */ private Class clazz; /** shared instance */ private transient SharedClassObject object; /** Constructor. * @param the instance */ public WriteReplace (SharedClassObject object) { this.object = object; this.clazz = object.getClass (); } /** Write object. */ private void writeObject (ObjectOutputStream oos) throws IOException { oos.defaultWriteObject (); object.writeExternal (oos); } /** Read object. */ private void readObject (ObjectInputStream ois) throws IOException, ClassNotFoundException { ois.defaultReadObject (); object = findObject (clazz, true); object.readExternal (ois); } /** Read resolve to the read object. */ private Object readResolve () { return object; } } /** The inner class that encapsulates the shared data together with * a reference counter */ static final class DataEntry extends Object { /** The data */ private HashMap map; /** The reference counter */ private int count = 0; /** weak reference to an object of this class */ private WeakReference ref = new WeakReference (null); /** Returns the data * @param obj the requestor object * @return the data */ Map getMap (SharedClassObject obj) { if (map == null) { // to signal invalid state map = new HashMap (); // no data for this class yet obj.initialize (); } return map; } /** Increases the counter (thread safe) * @return new counter value */ int increase () { return ++count; } /** Dereases the counter (thread safe) * @return new counter value */ int decrease () { return --count; } /** Request for first object. If there is none, use the requestor * @param obj requestor * @return the an object of this type */ SharedClassObject first (SharedClassObject obj) { SharedClassObject s = (SharedClassObject)ref.get (); if (s == null) { ref = new WeakReference (obj); return obj; } else { return s; } } /** @return shared object or null */ public SharedClassObject get () { return (SharedClassObject)ref.get (); } } } /* * Log * 11 Gandalf 1.10 1/13/00 Ian Formanek NOI18N * 10 Gandalf 1.9 1/12/00 Pavel Buzek I18N * 9 Gandalf 1.8 10/22/99 Ian Formanek NO SEMANTIC CHANGE - Sun * Microsystems Copyright in File Comment * 8 Gandalf 1.7 10/4/99 Jesse Glick Better exception * reporting. * 7 Gandalf 1.6 9/30/99 Jaroslav Tulach DataLoader is now * serializable. * 6 Gandalf 1.5 6/8/99 Ian Formanek ---- Package Change To * org.openide ---- * 5 Gandalf 1.4 4/26/99 Jesse Glick [JavaDoc] * 4 Gandalf 1.3 3/4/99 Jaroslav Tulach API cleaning * 3 Gandalf 1.2 2/19/99 David Simonek menu related changes... * 2 Gandalf 1.1 2/8/99 Jaroslav Tulach HashMap with smaller * size * 1 Gandalf 1.0 1/5/99 Ian Formanek * $ * Beta Change History: * 0 Tuborg 0.33 --/--/98 Jaroslav Tulach minimal class */